# Python3_Business_Indicator.py
# Create By GF 2023-11-10 23:59

# ----------------------------------------------------------------------------------------------------

# --------------------------------------------------
# ----------------- BizInd Sample ------------------
# BizInd = {"MA5"            :[],
#           "MA20"           :[],
#           "EMA12"          :[],
#           "EMA26"          :[]}
# --------------------------------------------------

# ----------------------------------------------------------------------------------------------------

# 商业指标(Business Indicator): 简单移动平均(Simple Moving Average).
def BizInd_SMA(Period:int, Price_List:list) -> list:
    
    SMA_List = []
    # --------------------------------------------------
    for Idx in range(0, len(Price_List)):
        # 以 Period = 5 为例，要指向列表第5个值，其索引为4。
        if Idx < (Period - 1):
            # ------------------------------------------
            SMA_List.append(None)
        else:
            # Python 列表截取，含头不含尾。
            # 例如：要截取 [1, 2, 3, 4, 5, 6] 中的 [1, 2, 3, 4, 5]。
            #       需要使用 list[0:5] 而不是 list[0:4]。
            # ------------------------------------------
            Value = sum(Price_List[(Idx - Period + 1):(Idx + 1)]) / Period
            # ------------------------------------------
            SMA_List.append(round(Value, 4))
    # --------------------------------------------------
    return SMA_List

# 商业指标(Business Indicator): 指数移动平均(Exponential Moving Average).
def BizInd_EMA(Period:int, Price_List:list) -> list:
    
    ############ 首日EMA直接使用当日价格 #############
    # --------------------------------------------------
    # 公式：EMA = 当日价格 x 2/(N+1) + 前一日EMA x (N-1)/(N+1)
    # --------------------------------------------------
    # 以计算12日EMA举例：
    # 其中"i"代表当日日期。
    # EMA12 = 2/(12+1) * Price[i] + (12+1-2)/(12+1) * EMA12[i-1]
    # 首日EMA由于没有昨日EMA数据，所以首日EMA直接使用当日价格。
    # --------------------------------------------------
    EMA_List = []
    # --------------------------------------------------
    for Idx, Price in zip(range(0, len(Price_List)), Price_List):
        # ----------------------------------------------
        if Idx == 0:
            # ------------------------------------------
            EMA_List.append(Price)
        else:
            # ------------------------------------------
            Value = 2/(Period+1) * Price + (Period+1-2)/(Period+1) * EMA_List[Idx - 1]
            # ------------------------------------------
            EMA_List.append(round(Value, 4))
    # --------------------------------------------------
    return EMA_List

# 商业指标(Business Indicator): 相对强弱指标 (Relative Strength Index).
def BizInd_RSI(Period:int, Price_List:list) -> list:

    # 相对强弱指标 RSI 是用以计测市场供需关系和买卖力道的方法及指标。
    # 
    # 公式一:
    # RSI(N) = A ÷ ( A + B ) × 100
    # A = N 日内价格所有上涨额度之和
    # B = N 日内价格所有下跌额度之和(取正数, 即乘以(-1))
    # 
    # 公式二:
    # RS(相对强度) = N日内价格所有上涨额度之和的平均值 ÷ N日内价格所有下跌额度之和的平均值(取绝对值)
    # RSI(相对强弱指标) = 100 - 100 ÷ ( 1 + RS )
    # 
    # 这两个公式虽然有些不同, 但计算的结果一样。
    # 
    # 股票 RSI 三条线分别为 RSI1, RSI2, RSI3。
    # RSI1 是白线，一般指 6 日相对强弱指标;
    # RSI2 是黄线，一般指 12 日相对强弱指标;
    # RSI3 是紫线，一般指 24 日相对强弱指标.
    # --------------------------------------------------
    Rise_Fall_Amt_List:list = []
    # --------------------------------------------------
    for i in range(0, len(Price_List)):
        if i == 0: Rise_Fall_Amt_List.append(0.0)
        if i >= 1: Rise_Fall_Amt_List.append(Price_List[i] - Price_List[i-1])
    # --------------------------------------------------
    RSI_List:list = []
    # --------------------------------------------------
    for i in range(0, len(Rise_Fall_Amt_List)):
        # 以 Period = 5 为例，要指向列表第5个值，其索引为4。
        if i < (Period - 1):
            RSI_List.append(None)
        else:
            Rise_Amt_Sum_Within_Period:float = 0.0 # 周期内的上涨之和.
            Fall_Amt_Sum_Within_Period:float = 0.0 # 周期内的下跌之和.
            # ------------------------------------------
            Rise_Amt_Sum_Within_Period = sum([Rise_Fall_Amt_List[Idx] for Idx in range((i - Period + 1), (i + 1)) if Rise_Fall_Amt_List[Idx] >= 0])
            Fall_Amt_Sum_Within_Period = sum([Rise_Fall_Amt_List[Idx] * (-1) for Idx in range((i - Period + 1), (i + 1)) if Rise_Fall_Amt_List[Idx] < 0])
            # ------------------------------------------
            # 每天都是下跌, 这将导致没有Up Move的日期, 最近N天的所有的Up Move之和是0, Down Move会是某个正数,0 除以某个正数是0, 所以这种特殊情况会定义RSI为0.
            if Rise_Amt_Sum_Within_Period == 0.0 and Fall_Amt_Sum_Within_Period != 0.0: RSI_List.append(0.0)
            # ------------------------------------------
            # 每天都是上涨, 这将导致没有Down Move的日期, 最近N天的所有的Down Move之和是0, RS会是某个正数除以0, 数学上这是非法的, 所以这种特殊情况会定义RSI为100.
            if Fall_Amt_Sum_Within_Period == 0.0 and Rise_Amt_Sum_Within_Period != 0.0: RSI_List.append(100.0)
            # ------------------------------------------
            # 每天既没上涨也没下跌, 最近N天的所有的Up Move之和是0, 最近N天的所有的Down Move之和是0, RS会是0除以0, 但实际并不处于每天都是上涨或每天都是下跌的情况, 所以行情属于中性, 这种特殊情况定义RSI为50.
            if Fall_Amt_Sum_Within_Period == 0.0 and Rise_Amt_Sum_Within_Period == 0.0: RSI_List.append(50.0)
            # ------------------------------------------
            if Rise_Amt_Sum_Within_Period != 0.0 and Fall_Amt_Sum_Within_Period != 0.0:
                RS:float = (Rise_Amt_Sum_Within_Period / Period) / (Fall_Amt_Sum_Within_Period / Period)
                # --------------------------------------
                RSI:float = (100 - 100 / (1 + RS))
                # --------------------------------------
                RSI_List.append(round(RSI, 4))
    # --------------------------------------------------
    return RSI_List

# ----------------------------------------------------------------------------------------------------
# EOF
